/**
 * 合并策略
 * 
 */


import config from '../config'
import { warn } from './debug'
import { set } from '../observer/index'
import { unicodeRegExp } from './lang'
import { nativeWatch, hasSymbol } from './env'

import {
  ASSET_TYPES, // 属性
  LIFECYCLE_HOOKS // 属性的合并策略
} from 'shared/constants'

import {
  extend,
  hasOwn,
  camelize,
  toRawType,
  capitalize,
  isBuiltInTag,
  isPlainObject
} from 'shared/util'

/**
 * 所有的策略
 */
const strats = config.optionMergeStrategies

if (process.env.NODE_ENV !== 'production') {
  /**
   * el策略
   */
  strats.el = strats.propsData = function (parent, child, vm, key) {
    if (!vm) { // 只允许vue实例才拥有el属性，其他子类构造器不允许有el属性
      warn(
        `option "${key}" can only be used during instance ` +
        'creation with the `new` keyword.'
      )
    }
    // 默认合并策略  儿子有取儿子  否则取父亲  即儿子真  则覆盖父亲
    return defaultStrat(parent, child)
  }
}

/**
 * to: 子data
 * from: 父data
 * 将父data合并到子data中 如果子父冲突   保留子
 */
function mergeData (to, from) {
  if (!from) return to
  let key, toVal, fromVal

  const keys = hasSymbol
    ? Reflect.ownKeys(from) // Reflect.ownKeys可以拿到Symbol类型的数据
    : Object.keys(from)

  for (let i = 0; i < keys.length; i++) {
    key = keys[i]
    if (key === '__ob__') continue   // 遇到__ob__属性  跳过执行下一次
    toVal = to[key]
    fromVal = from[key]
    if (!hasOwn(to, key)) {
      // 如果儿子的数据  父亲没有  则将儿子的数据添加到响应式系统中
      set(to, key, fromVal) // 调用set实现响应式
    } else if (
      toVal !== fromVal &&
      isPlainObject(toVal) &&
      isPlainObject(fromVal)
    ) {
      // 如果是深层对象   合并时需要递归
      mergeData(toVal, fromVal)
    }
  }
  return to
}

export function mergeDataOrFn (
  parentVal,
  childVal,
  vm
) {

  if (!vm) {
    // 子父类
    if (!childVal) { // 儿子没有data  用父亲的
      return parentVal
    }
    if (!parentVal) { // 父亲是undefined  用儿子的
      return childVal
    }
    return function mergedDataFn () { // 父亲和儿子都有data  进行合并 mergeData两个入参，但是需要看儿子和父亲是否是函数   如果是函数  执行一下，否则直接用
      return mergeData(
        typeof childVal === 'function' ? childVal.call(this, this) : childVal,
        typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
      )
    }
  } else {
    // 实例
    return function mergedInstanceDataFn () {
      const instanceData = typeof childVal === 'function'
        ? childVal.call(vm, vm)
        : childVal
      const defaultData = typeof parentVal === 'function'
        ? parentVal.call(vm, vm)
        : parentVal
      if (instanceData) {
        // 实例上存在data 则合并
        return mergeData(instanceData, defaultData)
      } else {
        // TODO 暂时不理解什么时候会执行到这个分支
        console.log('实例没有datarguments,返回vm构造函数上的data属性选项')
        return defaultData
      }
    }
  }
}

/**
 * data的合并策略
 * 
 * 并不是直接将两个data对象合并，而是返回了两个函数
 * 真正的合并时机在
 */
strats.data = function (
  parentVal,
  childVal,
  vm
) {
  if (!vm) { 
    if (childVal && typeof childVal !== 'function') { // 儿子存在则儿子的data必须是一个函数 ，这样做是为了每次通过函数创建一个独立的内存，生成一个对象，保证组价之间数据互补影响
      process.env.NODE_ENV !== 'production' && warn(
        'The "data" option should be a function ' +
        'that returns a per-instance value in component ' +
        'definitions.',
        vm
      )
      // 如果没有儿子或者儿子的data不是函数   直接返回父亲
      return parentVal
    }
    return mergeDataOrFn(parentVal, childVal)
  }
  // 实例和非实例的合并策略不同
  return mergeDataOrFn(parentVal, childVal, vm)
}


function mergeHook (
  parentVal,
  childVal
){
  const res = childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
  return res
    ? dedupeHooks(res)
    : res
}

function dedupeHooks (hooks) {
  const res = []
  for (let i = 0; i < hooks.length; i++) {
    if (res.indexOf(hooks[i]) === -1) {
      res.push(hooks[i])
    }
  }
  return res
}

LIFECYCLE_HOOKS.forEach(hook => {
  strats[hook] = mergeHook
})

function mergeAssets (
  parentVal,
  childVal,
  vm,
  key
) {
  // 创建一个空对象，protoType 指向父的对应属性
  const res = Object.create(parentVal || null)
  if (childVal) {
    process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm) // 校验components,filters,directives必须是对象
    return extend(res, childVal)
  } else {
    return res
  }
}

// 不同属性的合并策略
ASSET_TYPES.forEach(function (type) {
  strats[type + 's'] = mergeAssets
})

strats.watch = function (
  parentVal,
  childVal,
  vm,
  key
) {
  if (parentVal === nativeWatch) parentVal = undefined
  if (childVal === nativeWatch) childVal = undefined
  if (!childVal) return Object.create(parentVal || null)
  if (process.env.NODE_ENV !== 'production') {
    assertObjectType(key, childVal, vm)
  }
  if (!parentVal) return childVal
  const ret = {}
  extend(ret, parentVal)
  for (const key in childVal) {
    let parent = ret[key]
    const child = childVal[key]
    if (parent && !Array.isArray(parent)) {
      parent = [parent]
    }
    ret[key] = parent
      ? parent.concat(child)
      : Array.isArray(child) ? child : [child]
  }
  return ret
}

strats.props =
strats.methods =
strats.inject =
strats.computed = function (
  parentVal,
  childVal,
  vm,
  key
) {
  if (childVal && process.env.NODE_ENV !== 'production') {
    assertObjectType(key, childVal, vm)
  }
  if (!parentVal) return childVal
  const ret = Object.create(null)
  extend(ret, parentVal)
  if (childVal) extend(ret, childVal)
  return ret
}
strats.provide = mergeDataOrFn

const defaultStrat = function (parentVal, childVal) {
  return childVal === undefined
    ? parentVal
    : childVal
}

// 校验儿子组件名称的合法性
function checkComponents (options) {
  for (const key in options.components) {
    validateComponentName(key)
  }
}

export function validateComponentName (name) {
  if (!new RegExp(`^[a-zA-Z][\\-\\.0-9_${unicodeRegExp.source}]*$`).test(name)) {
    warn(
      'Invalid component name: "' + name + '". Component names ' +
      'should conform to valid custom element name in html5 specification.'
    )
  }
  if (isBuiltInTag(name) || config.isReservedTag(name)) {
    warn(
      'Do not use built-in or reserved HTML elements as component ' +
      'id: ' + name
    )
  }
}

// props规范校验
function normalizeProps (options, vm) {
  const props = options.props
  if (!props) return
  const res = {}
  let i, val, name
  if (Array.isArray(props)) {
    i = props.length
    while (i--) {
      val = props[i]
      if (typeof val === 'string') {
        name = camelize(val)
        // 将数组形式的props遍历成对象格式
        res[name] = { type: null }
      } else if (process.env.NODE_ENV !== 'production') {
        warn('props must be strings when using array syntax.')
      }
    }
  } else if (isPlainObject(props)) {
    for (const key in props) {
      val = props[key]
      name = camelize(key)
      res[name] = isPlainObject(val)
        ? val
        : { type: val }
    }
  } else if (process.env.NODE_ENV !== 'production') {
    warn(
      `Invalid value for option "props": expected an Array or an Object, ` +
      `but got ${toRawType(props)}.`,
      vm
    )
  }
  options.props = res
}

// inject规范校验
function normalizeInject (options, vm) {
  const inject = options.inject
  if (!inject) return
  const normalized = options.inject = {}
  if (Array.isArray(inject)) {
    for (let i = 0; i < inject.length; i++) {
      // from: 属性是在可用的注入内容中搜索用的 key
      normalized[inject[i]] = { from: inject[i] }
    }
  } else if (isPlainObject(inject)) {
    for (const key in inject) {
      const val = inject[key]
      normalized[key] = isPlainObject(val)
        ? extend({ from: key }, val)
        : { from: val }
    }
  } else if (process.env.NODE_ENV !== 'production') {
    warn(
      `Invalid value for option "inject": expected an Array or an Object, ` +
      `but got ${toRawType(inject)}.`,
      vm
    )
  }
}

// 指令规范化
function normalizeDirectives (options) {
  const dirs = options.directives
  if (dirs) {
    for (const key in dirs) {
      const def = dirs[key]
      if (typeof def === 'function') {
        dirs[key] = { bind: def, update: def }
      }
    }
  }
}

function assertObjectType (name, value, vm) {
  if (!isPlainObject(value)) {
    warn(
      `Invalid value for option "${name}": expected an Object, ` +
      `but got ${toRawType(value)}.`,
      vm
    )
  }
}

/**
 * 合并options
 * 
 * 接收一个父亲
 * 接收一个儿子
 * 接收一个实例
 * 
 * 返回合并后的选项
 */
export function mergeOptions (
  parent,
  child,
  vm
) {
  if (process.env.NODE_ENV !== 'production') {
    checkComponents(child) // 非生产环境  校验儿子组件名称的合法性
  }

  // TODO 儿子什么时候是函数？  这个组件怎么写？
  if (typeof child === 'function') {
    child = child.options
  }

  normalizeProps(child, vm) // 校验props 必须是数组或者对象，是数组时内部应为字符串  props: ['name', 'age']  或props: {name: string}
  normalizeInject(child, vm)
  normalizeDirectives(child)

  if (!child._base) { // 如果子组件没有_base 说明这个子组件还有儿子   有extends或者mixins属性  则递归遍历
    if (child.extends) {
      parent = mergeOptions(parent, child.extends, vm)
    }
    if (child.mixins) {
      for (let i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm)
      }
    }
  }

  /**
   * 父亲和儿子分别遍历，优先使用传入的配置 否则使用默认配置
   * components
   * data
   * directives
   * filters
   * render
   * _base
   */
  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    // 执行各自的合并策略
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

export function resolveAsset (
  options,
  type,
  id,
  warnMissing
) {
  if (typeof id !== 'string') {
    return
  }
  const assets = options[type]
  if (hasOwn(assets, id)) return assets[id]
  const camelizedId = camelize(id)
  if (hasOwn(assets, camelizedId)) return assets[camelizedId]
  const PascalCaseId = capitalize(camelizedId)
  if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]
  const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
  if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
    warn(
      'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
      options
    )
  }
  return res
}
